package editor.animation;

import javax.swing.*;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;

// see http://www.javaworld.com/article/2077601/learn-java/java-tip-97--add-drag-and-drop-to-your-jtrees.html
// only moving is implemented (no copying)
public abstract class DnDJTree implements DragGestureListener {
	public DnDJTree(JTree tree, Class flavorClass, String flavorString) {
		this.tree = tree;
		this.dragSource = new DragSource();
		this.flavorClass = flavorClass;
		this.flavorString = flavorString;

		recognizer = dragSource.createDefaultDragGestureRecognizer(tree, DnDConstants.ACTION_MOVE, this);
		target = new DropTarget(tree, dropTargetListener);
	}

	private JTree tree;
	private DragSource dragSource;
	private DragSourceListener dragSourceListener = new DragSourceListenerImpl();
	private DropTargetListener dropTargetListener = new DropTargetListenerImpl();
	private DragGestureRecognizer recognizer;
	private DropTarget target;
	private Class flavorClass;
	private String flavorString;

	// should return a Transferable, given a node in the tree
	public abstract Transferable getTransferable(Object node);
	// should return a node, given a Transferable
	public abstract Object getNode(Transferable transferable);

	public enum Where {
		ABOVE,
		BELOW,
		CHILD
	}

	public abstract void moveNodeRequest(Object draggedNode, Object droppedNode, Where where);

	// DragGestureListener
	@Override
	public void dragGestureRecognized(DragGestureEvent dge) {
		// only allow moving
		if (dge.getDragAction() != DnDConstants.ACTION_MOVE) {
			return;
		}

		TreePath path = tree.getSelectionPath();
		Object dragNode = (path == null) ? null : path.getLastPathComponent();

		if (dragNode == null) {
			return;
		}

		Transferable transferable = getTransferable(dragNode);
 		Cursor cursor = DragSource.DefaultMoveDrop;
		dragSource.startDrag(dge, cursor, transferable, dragSourceListener);
	}

	// DragSourceListener
	private class DragSourceListenerImpl implements DragSourceListener {
		public void dragEnter(DragSourceDragEvent dsde) {
			dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
		}
		public void dragOver(DragSourceDragEvent dsde) {}
		public void dropActionChanged(DragSourceDragEvent dsde) {}
		public void dragExit(DragSourceEvent dse) {
			dse.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
		}
		public void dragDropEnd(DragSourceDropEvent dsde) {}
	}

	// DropTargetListener
	private class DropTargetListenerImpl implements DropTargetListener {
		public void dragEnter(DropTargetDragEvent dtde) {}
		public void dragOver(DropTargetDragEvent dtde) {}
		public void dropActionChanged(DropTargetDragEvent dtde) {}
		public void dragExit(DropTargetEvent dte) {}
		public void drop(DropTargetDropEvent dtde) {
			Transferable transferable = dtde.getTransferable();

			if (!transferable.isDataFlavorSupported(new DataFlavor(flavorClass, flavorString))) {
				dtde.rejectDrop();
				return;
			}

			Object draggedNode = getNode(transferable);

			Point loc = dtde.getLocation();
			TreePath path = tree.getPathForLocation(loc.x, loc.y);
			if (path == null) {
				dtde.rejectDrop();
				return;
			}

			// determine what region of the node we're in
			Object node = path.getLastPathComponent();
			int row = tree.getRowForPath(path);
			Rectangle rect = tree.getRowBounds(row);
			// if the point is in the top 25%, move node above; if in bottom 25%, move node below
			// if in middle 50%, move node as child
			int percent25 = rect.height / 4;
			if (loc.y < rect.y + percent25) {
				// drop above
				moveNodeRequest(draggedNode, node, Where.ABOVE);
			} else if (loc.y > rect.y + rect.height - percent25) {
				// drop below
				moveNodeRequest(draggedNode, node, Where.BELOW);
			} else {
				// drop as child
				moveNodeRequest(draggedNode, node, Where.CHILD);
			}
		}
	}
}
